//****************************************************************************
// Copyright (C) 2001,2002,2003,2004  PEAK System-Technik GmbH
//
// linux@peak-system.com
// www.peak-system.com
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// Maintainer(s): Klaus Hitschler (klaus.hitschler@gmx.de)
//
// Contributions: Philipp Baer (philipp.baer@informatik.uni-ulm.de)
//****************************************************************************

//****************************************************************************
//
// all parts to handle the interface specific parts of pcan-pci
// 
// $Log: pcan_pci.c,v $
// Revision 1.35  2004/04/13 19:43:35  klaus
// release_20040413_x; improved timestamp calculation for USB
//
// Revision 1.34  2004/04/11 22:03:29  klaus
// cosmetic changes
//
// Revision 1.33  2004/04/10 12:25:39  klaus
// merge polished between HEAD and kernel-2.6 branch
//
// Revision 1.32  2004/04/10 08:57:27  klaus
// merge finished between HEAD and kernel-2.6 branch
//
// Revision 1.30.2.1  2004/03/21 12:09:09  klaus
// first commit for branch to kernel 2.6 code
//
// Revision 1.31  2004/03/19 22:25:27  klaus
// removed wrong call to dev->cleanup when no PCI irq was assigned, contributed by Garth Zeglin
//
// Revision 1.30  2003/11/19 22:52:48  klaus
// removed obsolete probe function
//
// Revision 1.29  2003/06/22 15:34:51  klaus
// added parts to support devfs provided by Philipp Baer (partially untested)
//
// Revision 1.28  2003/03/02 10:58:07  klaus
// merged USB thread into main path
//
// Revision 1.27  2003/03/02 10:58:07  klaus
// merged USB thread into main path
//
// Revision 1.26.2.5  2003/01/29 20:34:20  klaus
// release_20030129_a and release_20030129_u released
//
// Revision 1.26.2.4  2003/01/29 20:34:20  klaus
// release_20030129_a and release_20030129_u released
//
// Revision 1.26.2.3  2003/01/28 23:28:26  klaus
// reorderd pcan_usb.c and pcan_usb_kernel.c, tidied up
//
// Revision 1.26.2.2  2003/01/14 20:31:53  klaus
// read/write/minor assigment is working
//
//****************************************************************************

//****************************************************************************
// INCLUDES
#include <src/pcan_common.h>     // must always be the 1st include
#include <linux/ioport.h>
#include <asm/io.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <src/pcan_pci.h>
#include <src/pcan_sja1000.h>

//****************************************************************************
// DEFINES
#define PCAN_PCI_MINOR_BASE 0        // the base of all pci device minors

// important PITA registers
#define PITA_ICR         0x00        // interrupt control register
#define PITA_GPIOICR     0x18        // general purpose IO interface control register
#define PITA_MISC        0x1C        // miscellanoes register

//****************************************************************************
// GLOBALS

//****************************************************************************
// LOCALS
static u16 pci_devices = 0; // count the number of pci devices

//****************************************************************************
// CODE  
static u8 pcan_pci_readreg(struct pcandev *dev, u8 port) // read a register
{
  u32 lPort = port << 2;
  return readb(dev->port.pci.pvVirtPort + lPort);
}

static void pcan_pci_writereg(struct pcandev *dev, u8 port, u8 data) // write a register
{
  u32 lPort = port << 2;
  writeb(data, dev->port.pci.pvVirtPort + lPort);
}

// a special frame around the default irq handler
static irqreturn_t pcan_pci_irqhandler(int irq, void *dev_id, struct pt_regs *regs)
{
  struct pcandev *dev = (struct pcandev *)dev_id;
  u16 PitaICRLow;

  // select and clear in Pita stored interrupt 
  PitaICRLow = readw(dev->port.pci.pvVirtConfigPort);  // PITA_ICR
  if (dev->port.pci.ucMasterDevice == CHANNEL_SLAVE)
  {
    if (PitaICRLow & 0x0001)
      writew(0x0001, dev->port.pci.pvVirtConfigPort);
  } 
  else // it is CHANNEL_SINGLE or CHANNEL_MASTER
  {
    if (PitaICRLow & 0x0002)
      writew(0x0002, dev->port.pci.pvVirtConfigPort);
  }  
  return sja1000_irqhandler(irq, dev_id, regs); 
}

// all about interrupt handling
static int pcan_pci_req_irq(struct pcandev *dev)
{
  int err;
  u16 PitaICRHigh;
  
  if (dev->wInitStep == 5)
  {
    if ((err = request_irq(dev->port.pci.wIrq, pcan_pci_irqhandler, IRQF_DISABLED | IRQF_SHARED, "pcan", dev)))
      return err;
    
    // enable interrupt depending if SLAVE, MASTER, SINGLE
    PitaICRHigh = readw(dev->port.pci.pvVirtConfigPort + PITA_ICR + 2);
    if (dev->port.pci.ucMasterDevice == CHANNEL_SLAVE)
      PitaICRHigh |= 0x0001;
    else
      PitaICRHigh |= 0x0002;  
    writew(PitaICRHigh, dev->port.pci.pvVirtConfigPort + PITA_ICR + 2); 
    
    dev->wInitStep++;
  }
  
  return 0;
}

static void pcan_pci_free_irq(struct pcandev *dev)
{
  u16 PitaICRHigh;
  
  if (dev->wInitStep == 6)
  {
    // disable interrupt, polarity 0
    PitaICRHigh = readw(dev->port.pci.pvVirtConfigPort + PITA_ICR + 2);
    if (dev->port.pci.ucMasterDevice == CHANNEL_SLAVE)
      PitaICRHigh &= ~0x0001;
    else
      PitaICRHigh &= ~0x0002;  
    writew(PitaICRHigh, dev->port.pci.pvVirtConfigPort + PITA_ICR + 2); 
  
    free_irq(dev->port.pci.wIrq, dev);  
    dev->wInitStep--;
  }
}

// release and probe
static int pcan_pci_cleanup(struct pcandev *dev)
{
  DPRINTK(KERN_DEBUG "%s: pcan_pci_cleanup()\n", DEVICE_NAME);

  switch(dev->wInitStep)
  {
    case 6: pcan_pci_free_irq(dev);
    case 5: pci_devices--; 
    case 4: iounmap(dev->port.pci.pvVirtPort);
    case 3: 
            #ifndef LINUX_22
            release_mem_region(dev->port.pci.dwPort, PCI_PORT_SIZE);
	          #endif
    case 2: if (dev->port.pci.ucMasterDevice != CHANNEL_SLAVE)
              iounmap(dev->port.pci.pvVirtConfigPort);  
    case 1: 
            #ifndef LINUX_22
            if (dev->port.pci.ucMasterDevice != CHANNEL_SLAVE)
              release_mem_region(dev->port.pci.dwConfigPort, PCI_CONFIG_PORT_SIZE);  
	          #endif
    case 0: dev->wInitStep = 0;
  }
  
  return 0;
}

// interface depended open and close
static int pcan_pci_open(struct pcandev *dev)
{
  return 0;
}

static int pcan_pci_release(struct pcandev *dev)
{
  return 0;
}

int  pcan_pci_init(struct pcandev *dev, u32 dwConfigPort, u32 dwPort, u16 wIrq, u8 ucMasterDevice, struct pcandev *master_dev)
{
  DPRINTK(KERN_DEBUG "%s: pcan_pci_init(), pci_devices = %d\n", DEVICE_NAME, pci_devices);

  dev->port.pci.ucMasterDevice = ucMasterDevice;
    
  // init process wait queues
  init_waitqueue_head(&dev->read_queue);
  init_waitqueue_head(&dev->write_queue);
    
  // set this before any instructions, fill struct pcandev, part 1 
	dev->wInitStep   = 0;  
  dev->readreg     = pcan_pci_readreg;
  dev->writereg    = pcan_pci_writereg;
  dev->cleanup     = pcan_pci_cleanup;
  dev->req_irq     = pcan_pci_req_irq;
  dev->free_irq    = pcan_pci_free_irq;
  dev->open        = pcan_pci_open;
  dev->release     = pcan_pci_release;
	dev->nMinor      = PCAN_PCI_MINOR_BASE + pci_devices; 
           
  // fill struct pcandev, part 1
  dev->port.pci.dwPort       = dwPort;
  dev->port.pci.dwConfigPort = dwConfigPort;
  dev->port.pci.wIrq         = wIrq;

  // reject illegal combination
  if (!dwPort || !wIrq)
    return -EINVAL;

  // do it only if the device is channel master
  if (dev->port.pci.ucMasterDevice != CHANNEL_SLAVE)
  {
    #ifndef LINUX_22
    if (check_mem_region(dev->port.pci.dwConfigPort, PCI_CONFIG_PORT_SIZE))
      return -EBUSY;
            
    request_mem_region(dev->port.pci.dwConfigPort, PCI_CONFIG_PORT_SIZE, "pcan");
    #endif
    
    dev->wInitStep = 1;
    
    dev->port.pci.pvVirtConfigPort = ioremap(dwConfigPort, PCI_CONFIG_PORT_SIZE); 
    if (dev->port.pci.pvVirtConfigPort == NULL)
	    return -ENODEV;
	    
    dev->wInitStep = 2;
    
    // configuration of the PCI chip, part 2
    writew(0x0005, dev->port.pci.pvVirtConfigPort + PITA_GPIOICR + 2);  //set GPIO control register
    
    if (dev->port.pci.ucMasterDevice == CHANNEL_MASTER)
      writeb(0x00, dev->port.pci.pvVirtConfigPort + PITA_GPIOICR); // enable both
    else
      writeb(0x04, dev->port.pci.pvVirtConfigPort + PITA_GPIOICR); // enable single
      
    writeb(0x05, dev->port.pci.pvVirtConfigPort + PITA_MISC + 3);  // toggle reset
    mdelay(5);
    writeb(0x04, dev->port.pci.pvVirtConfigPort + PITA_MISC + 3);  // leave parport mux mode
    wmb();
  }
  else
    dev->port.pci.pvVirtConfigPort = master_dev->port.pci.pvVirtConfigPort;
  
  #ifndef LINUX_22
	if (check_mem_region(dev->port.pci.dwPort, PCI_PORT_SIZE))
		return -EBUSY;
            
  request_mem_region(dev->port.pci.dwPort, PCI_PORT_SIZE, "pcan");
  #endif
  
  dev->wInitStep = 3;
  
  dev->port.pci.pvVirtPort = ioremap(dwPort, PCI_PORT_SIZE); 
    
  if (dev->port.pci.pvVirtPort == NULL)
    return -ENODEV;
  dev->wInitStep = 4;
  
  pci_devices++;
  dev->wInitStep = 5; 
  
  printk(KERN_INFO "%s: pci device minor %d found\n", DEVICE_NAME, dev->nMinor);
    
  return 0;
}


